package fr.inserm.u915.tabixserver;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

@SuppressWarnings("serial")
public class WSDLServlet extends HttpServlet
	{
	static private abstract class Param
		{
		int minOccur=1;
		Integer maxOccur=null;

		String name;
		String type=XSD.prefix+":string";
		Param(String name)
			{
			this.name=name;
			}
		Param(String name,String type)
			{
			this(name);
			this.type=type;
			}
		
		void writeSchema(XMLStreamWriter w) throws XMLStreamException
			{
			w.writeStartElement(XSD.prefix,"element",XSD.NS);
			w.writeAttribute("type",this.type);
			w.writeAttribute("name",this.name);
			w.writeAttribute("minOccurs",String.valueOf(minOccur));
			if(maxOccur!=null)
				{
				w.writeAttribute("maxOccurs",String.valueOf(maxOccur));
				}
			else
				{
				w.writeAttribute("maxOccurs","unbounded");
				}
			w.writeEndElement();
			}

		}
	
	static private class Response extends Param
		{
		Response(String name)
			{
			super(name);
			}
		Response(String name,String retType)
			{
			super(name,retType);
			}
		}

	static private class Argument extends Param
		{
		Argument(String name)
			{
			super(name);
			}
		
		
			Argument(String name,String retType)
				{
				super(name,retType);
				}

		}

	
	
	static private class Operation
		{
		String name;
		String documentation=null;
		Response response=null;
		boolean hasException=false;
		List<Argument> args=new ArrayList<WSDLServlet.Argument>();
		Operation(String name)
			{
			this.name=name;
			}
		
		void writePortTypeOperation(XMLStreamWriter w) throws XMLStreamException
			{
			w.writeStartElement(WSDL.NS,"operation");
			w.writeAttribute("name",this.name);
			
			if(documentation!=null)
				{
				w.writeStartElement(WSDL.NS,"documentation");
				w.writeCharacters(this.documentation);
				w.writeEndElement();
				}
			
			w.writeEmptyElement(WSDL.NS, "input");
			w.writeAttribute("message", TNS.prefix+":"+name);
			w.writeEmptyElement(WSDL.NS, "output");
			w.writeAttribute("message", TNS.prefix+":"+name+"Response");
			if(hasException)
				{
				w.writeEmptyElement(WSDL.NS, "fault");
				w.writeAttribute("message", TNS.prefix+":"+"exceptionType");
				w.writeAttribute("name", "exception");
				}
			w.writeEndElement();
			}
		
		void writeBindingOperation(XMLStreamWriter w) throws XMLStreamException
			{
			w.writeStartElement(WSDL.NS,"operation");
			w.writeAttribute("name",this.name);
			w.writeEmptyElement(SOAP.prefix, "operation",SOAP.NS);
			w.writeAttribute("soapAction","");
			
			for(String s: new String[]{"input","output"})
				{
				w.writeStartElement(WSDL.NS,s);
				w.writeEmptyElement(SOAP.prefix, "body",SOAP.NS);
				w.writeAttribute("use","literal");
				w.writeEndElement();
				}
			w.writeEndElement();
			}

		
		
		void writeMessage(XMLStreamWriter w) throws XMLStreamException
			{
			for(String s: new String[]{name,name+"Response"})
				{
				w.writeStartElement(WSDL.NS,"message");
				w.writeAttribute("name",s);
				w.writeEmptyElement(WSDL.NS, "part");
				w.writeAttribute("name","parameters");
				w.writeAttribute("element", TNS.prefix+":"+s);
				w.writeEndElement();
				}
			}
		
		void writeSchema(XMLStreamWriter w) throws XMLStreamException
			{
			w.writeComment("BEGIN: Schema for operation "+this.name);
			//query
			w.writeStartElement(XSD.prefix,"complexType",XSD.NS);
			w.writeAttribute("name",this.name);
			w.writeStartElement(XSD.prefix,"sequence",XSD.NS);
			for(Argument a:this.args)
				{
				a.writeSchema(w);
				}
			w.writeEndElement();
			w.writeEndElement();
			
			//response
			w.writeStartElement(XSD.prefix,"complexType",XSD.NS);
			w.writeAttribute("name",this.name+"Response");
			w.writeStartElement(XSD.prefix,"sequence",XSD.NS);
			if(this.response!=null) this.response.writeSchema(w);
			w.writeEndElement();
			w.writeEndElement();
			
			//instance of type
			for(String suffix:new String[]{"","Response"})
				{
				w.writeStartElement(XSD.prefix,"element",XSD.NS);
				w.writeAttribute("name",this.name+suffix);
				w.writeAttribute("type",TNS.prefix+":"+this.name+suffix);
				w.writeEndElement();//element
				}
			
			w.writeComment("END: Schema for operation "+this.name);
			}
		}
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException
		{
		final Model model=(Model)this.getServletContext().getAttribute(TabixCtxListener.TABIX_MODEL_ATTRIBUTE);
		if(model==null) throw new ServletException("model?");
		OutputStream out=resp.getOutputStream();
		Build build=(Build)req.getAttribute("build");
		if(build==null) throw new ServletException("build?");
		try
			{
			List<Operation> operations=new ArrayList<Operation>();
			Operation op=new Operation("getTables");
			op.documentation="Fetch the tables definitions installed on this server.";
			op.response=new Response("tableDef");
			op.response.type=TNS.prefix+":"+"tableDef";
			op.response.minOccur=0;
			op.response.maxOccur=null;
			operations.add(op);
			
			op=new Operation("getTableById");
			op.documentation="Search a tables definition by its ID. returns null id the table was not found.";
			op.response=new Response("tableDef");
			op.response.type=TNS.prefix+":"+"tableDef";
			op.response.minOccur=0;
			op.response.maxOccur=1;
			Argument arg0=new Argument("id");
			arg0.minOccur=1;
			arg0.maxOccur=1;
			op.args.add(arg0);
			operations.add(op);
			for(int i=0;i< 2;++i)
				{
				op=new Operation(i==0?"fetch":"count");
				if(i==0)
					{
					op.response=new Response(Instance.GENOMIC_OBJECT);
					op.response.type=TNS.prefix+":"+Instance.ABSTRACT_CHROM_START_END;
					op.response.minOccur=0;
					op.response.maxOccur=null;
					}
				else
					{
					op.response=new Response("total");
					op.response.type=XSD.prefix+":int";
					op.response.minOccur=1;
					op.response.maxOccur=1;
					}
				arg0=new Argument("tableid");
				arg0.minOccur=1;
				arg0.maxOccur=1;
				op.args.add(arg0);
				arg0=new Argument("chrom");
				arg0.minOccur=1;
				arg0.maxOccur=1;
				op.args.add(arg0);
				arg0=new Argument("chromStart",XSD.prefix+":int");
				arg0.minOccur=0;
				arg0.maxOccur=1;
				op.args.add(arg0);
				arg0=new Argument("chromEnd",XSD.prefix+":int");
				arg0.minOccur=0;
				arg0.maxOccur=1;
				op.args.add(arg0);
				arg0=new Argument("limit",XSD.prefix+":int");
				arg0.minOccur=0;
				arg0.maxOccur=1;
				op.args.add(arg0);
				operations.add(op);
				}
			
			XMLOutputFactory xmlfactory= XMLOutputFactory.newInstance();
			XMLStreamWriter w= xmlfactory.createXMLStreamWriter(out,resp.getCharacterEncoding());
			w.setDefaultNamespace(WSDL.NS);
			w.writeStartDocument(resp.getCharacterEncoding(),"1.0");
			w.writeStartElement(WSDL.NS,"definitions");
			w.writeAttribute("xmlns", WSDL.NS);
			w.writeAttribute("xmlns:"+XSD.prefix, XSD.NS);
			w.writeAttribute("xmlns:"+SOAP.prefix, SOAP.NS);
			w.writeAttribute("xmlns:"+TNS.prefix, TNS.NS);
			w.writeAttribute("targetNamespace", TNS.NS);
			w.writeAttribute("name","Service"+build.getId());
			
			
			/** types */
			w.writeStartElement(WSDL.NS,"types");
			w.writeStartElement(XSD.prefix,"schema",XSD.NS);
			w.writeAttribute("version","1.0");
			w.writeAttribute("targetNamespace",TNS.NS);
			
			/* schema : exception */
			w.writeStartElement(XSD.prefix,"complexType",XSD.NS);
			w.writeAttribute("name","Exception");
			w.writeStartElement(XSD.prefix,"sequence",XSD.NS);
			
				w.writeStartElement(XSD.prefix,"element",XSD.NS);
				w.writeAttribute("name","message");
				w.writeAttribute("type",XSD.prefix+":string");
				w.writeAttribute("minOccurs","0");
				w.writeEndElement();
				
			w.writeEndElement();//sequence
			w.writeEndElement();//complexType
			
			w.writeStartElement(XSD.prefix,"element",XSD.NS);
			w.writeAttribute("name", "Exception");
			w.writeAttribute("type", TNS.prefix+":"+"Exception");
			w.writeEndElement();//element

			
			/* schema : column */
			w.writeStartElement(XSD.prefix,"complexType",XSD.NS);
			w.writeAttribute("name","columnDef");
			w.writeStartElement(XSD.prefix,"sequence",XSD.NS);
			for(String s:new String[]{"id","label","description"})
				{
				w.writeStartElement(XSD.prefix,"element",XSD.NS);
				w.writeAttribute("name",s);
				w.writeAttribute("type",XSD.prefix+":string");
				w.writeEndElement();
				}
			w.writeEndElement();
			w.writeEndElement();
			
			w.writeStartElement(XSD.prefix,"element",XSD.NS);
			w.writeAttribute("name", "columnDef");
			w.writeAttribute("type", TNS.prefix+":"+"columnDef");
			w.writeEndElement();//element

			
			/* schema : table */
			w.writeStartElement(XSD.prefix,"complexType",XSD.NS);
			w.writeAttribute("name","tableDef");
			w.writeStartElement(XSD.prefix,"sequence",XSD.NS);
			for(String s:new String[]{"id","label","description"})
				{
				w.writeStartElement(XSD.prefix,"element",XSD.NS);
				w.writeAttribute("name",s);
				w.writeAttribute("type",XSD.prefix+":string");
				w.writeEndElement();
				}
			w.writeStartElement(XSD.prefix,"element",XSD.NS);
			w.writeAttribute("name","columns");
			w.writeAttribute("type",TNS.prefix+":"+"columnDef");
			w.writeAttribute("minOccurs","0");
			w.writeAttribute("maxOccurs","unbounded");
			w.writeEndElement();//element
			w.writeEndElement();//sequence
			w.writeEndElement();//complextype
			
			w.writeStartElement(XSD.prefix,"element",XSD.NS);
			w.writeAttribute("name", "tableDef");
			w.writeAttribute("type", TNS.prefix+":"+"tableDef");
			w.writeEndElement();//element
			
			for(Operation operation: operations)
				{
				operation.writeSchema(w);
				}
			
			
			//print all schema for all instances
			build.writeXsdBody(w);
			
			w.writeEndElement();//schema
			w.writeEndElement();//types
			/** message */
			for(Operation operation: operations)
				{
				operation.writeMessage(w);
				}
			/** portType */
			w.writeStartElement("portType");
			w.writeAttribute("name", "Port"+build.getId());
			for(Operation operation: operations)
				{
				operation.writePortTypeOperation(w);
				}
			w.writeEndElement();//portType
			
			/** binding */
			w.writeStartElement("binding");
			w.writeAttribute("name", "Binding"+build.getId());
			w.writeAttribute("type", TNS.prefix+":Port"+build.getId());
			
			w.writeEmptyElement(SOAP.prefix,"binding",SOAP.NS);
			w.writeAttribute("transport","http://schemas.xmlsoap.org/soap/http");
			w.writeAttribute("style","document");
			
			for(Operation operation: operations)
				{
				operation.writeBindingOperation(w);
				}
			w.writeEndElement();//binding
			
			/** service */
			w.writeStartElement("service");
			w.writeAttribute("name", "Service"+build.getId());
			w.writeStartElement("port");
			w.writeAttribute("name", "ServicePort"+build.getId());
			w.writeAttribute("binding", TNS.prefix+":" + "Binding"+build.getId());
			w.writeEmptyElement(SOAP.prefix,"address",SOAP.NS);
			w.writeAttribute("location",
					req.getScheme()+"://"+
					req.getServerName()+":"+
					req.getServerPort()
					+req.getServletContext().getContextPath()+"/tabix/build/"+
					build.getId()+"/soap"
					);
			w.writeEndElement();//port
			w.writeEndElement();//service
			
			w.writeEndDocument();
			w.flush();
			w.close();
			}
		catch(XMLStreamException err)
			{
			throw new IOException(err);
			}
		finally
			{
			out.flush();
			out.close();
			}
		}
	}
